/*
 * Copyright 2006-2007 S W McIlroy
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mockfu.junit;

import static org.mockfu.Exceptions.*;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;

import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.internal.runners.TestMethod;
import org.junit.runner.notification.RunNotifier;
import org.mockfu.ExpectationFailure;
import org.mockfu.Injection;
import org.mockfu.MockControl;

/**
 * JUnit 4.4 implementation.
 */
class FourDotFourVerifier extends JUnit4ClassRunner
{
    private final boolean parallelize;

    private final List<Future<?>> futures = new ArrayList<Future<?>>();
    private final ThreadLocal<MockControl<Verifier>> verifier = new InheritableThreadLocal<MockControl<Verifier>>();

    public FourDotFourVerifier(Class<?> klass, boolean parallelize) throws InitializationError
    {
        super(klass);
        this.parallelize = parallelize;
    }

    @Override
    protected void runMethods(RunNotifier notifier)
    {
        super.runMethods(notifier);

        if (parallelize)
        {
            try
            {
                for (Future<?> future : futures)
                    future.get();
            }
            catch (Exception ex)
            {
                throw undeclared(ex);
            }
        }
    }


    @Override
    protected void invokeTestMethod(final Method method, final RunNotifier notifier)
    {
        Runnable task = new Runnable()
        {
            public void run()
            {
                try
                {
                    verifier.set(MockControl.of(Verifier.class));
                    FourDotFourVerifier.super.invokeTestMethod(method, notifier);
                }
                finally
                {
                    Injection.ejectAll();
                }
            }
        };

        if (parallelize)
        {
            Set<String> mutexNames = new HashSet<String>();
            /* add mutex names from method annotation */
            Serialize serialize = method.getAnnotation(Serialize.class);
            if (serialize != null)
            {
                for (String mutex : serialize.around())
                {
                    if (mutex.contains(","))
                        for (String each : mutex.split(","))
                            mutexNames.add(each);
                    else
                        mutexNames.add(mutex);
                }
            }
            /* add mutex names from class annotation */
            serialize = method.getDeclaringClass().getAnnotation(Serialize.class);
            if (serialize != null)
            {
                for (String mutex : serialize.around())
                {
                    if (mutex.contains(","))
                        for (String each : mutex.split(","))
                            mutexNames.add(each);
                    else
                        mutexNames.add(mutex);
                }
            }

            String[] mutexArray = mutexNames.toArray(new String[mutexNames.size()]);
            futures.add(ParallelExecutor.submit(task, mutexArray));
        }
        else
            task.run();

    }

    @Override
    protected TestMethod wrapMethod(final Method method)
    {
        return new TestMethod(method, getTestClass())
        {
            public void invokeSuper(Object test)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
            {
                super.invoke(test);
            }

            @Override
            public void invoke(final Object test)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
            {
                try
                {
                    try
                    {
                        invokeSuper(test);
                    }
                    catch (InvocationTargetException ex)
                    {
                        /* replace exception with expectation failue if one exists:
                         * this is so that if an expectation failure got ignored it doesn't get lost */
                        ExpectationFailure failure = verifier.get().getFirstFailure();
                        if (failure != null)
                            ex = new InvocationTargetException(failure);
                        throw ex;
                    }
                    try
                    {
                        verifier.get().verifyAll();
                    }
                    catch (ExpectationFailure ex)
                    {
                        throw new InvocationTargetException(ex);
                    }
                }
                finally
                {
                    verifier.remove();
                }
            }
        };
    }


}